﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using log4net;

namespace AmoExtensions
{
    public class SsasPartitionManager
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        public void DoPartitionManagement(SsasPartitionManagerOptions options)
        {
            log.InfoFormat("Connecting to partition repository using connection string '{0}'", options.ConfigDatabaseConnectionString);

            string SsasServerName;
            PartitionRepository partitionRepository = new PartitionRepository(
                options.ConfigDatabaseConnectionString,
                options.Sql1ToListServers,
                options.Sql2ToListPartitions
            );

            try
            {
                SsasServerName = partitionRepository.SsasServerName(options.ServerType);
            }
            catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException)
            {
                string msg = string.Format("Failed to find SsasServerType='{0}' in the spm.SsasServer table. Check your configuration.", options.ServerType);
                throw new Exception(msg);
            }

            if (!SsasServerName.IsNullOrEmptyOrWhitespace())
            {
                using (AmoServer server = new AmoServer())
                {
                    try
                    {
                        IEnumerable<IPartition> listOfPartitions;

                        log.InfoFormat("Connecting to '{0}' server: '{1}'", options.ServerType, SsasServerName);

                        if (!server.Connect(SsasServerName))
                        {
                            string msg = string.Format("Failed to connect to the server {0}", SsasServerName);
                            throw new ApplicationException(msg);
                        }

                        try
                        {
                            // read the list of partitions we need to manage
                            // This gets a list of partitions we require in each measure group in each cube in each database on the server
                            listOfPartitions = partitionRepository.GetListOfPartitions(server);
                            log.InfoFormat("GetListOfPartitions found {0} partitions", listOfPartitions.Count());
                        }
                        catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException)
                        {
                            throw new Exception("Failed to find the spm.SsasPartitions view. Check your configuration.");
                        }

                        // deploy the database depending upon the command-line options
                        DoDeploymentOptions(server, options);

                        // always manage the partitions
                        ManagePartitions(server, listOfPartitions);

                        // process the database(s) depending upon the command-line options
                        DoProcessingOptions(server, options, listOfPartitions);
                    }
                    catch (System.Data.SqlClient.SqlException sqlex)
                    {
                        log.Fatal("Fatal database configuration error encountered: " + sqlex.Message);
                    }
                    catch (ApplicationException aex)
                    {
                        log.Error(aex.Message);
                    }
                    catch (AggregateException aex)
                    {
                        log.Error("Errors encoundered during database processing");
                        // we use AggregateExceptions to retrieve all the cube processing errors
                        // so we now need to loop through each inner exception
                        foreach (Exception ex in aex.InnerExceptions)
                        {
                            log.Error(ex.Message);
                        }
                    }
                    catch (Exception ex)
                    {
                        string msg = string.Format("Fatal error encountered: '{0}'", ex.Message);
                        log.Fatal(msg, ex);
                    }
                    log.InfoFormat("Disconnecting from '{0}' server: '{1}'", options.ServerType, SsasServerName);
                }
            }
            else
            {
                throw new Exception("SsasServerName is null or empty!");
            }
        }

        /// <summary>
        /// Deploys a cube or tabular model as determined by the command-line options
        /// </summary>
        /// <param name="server">A connected SSAS server (either Tabular or Multidimensional)</param>
        /// <param name="options">A populated SsasPartitionManagerOptions object containing the command-line options selected</param>
        public void DoDeploymentOptions(IAmoServer server, SsasPartitionManagerOptions options)
        {
            // now work out if we need to do any extra steps before we manage the cube partitions, by checking the command-line args
            // if no command line arguments, then simply manage partitions
            // if we have a filename and name of the cube, then deploy the cube and create partitions
            if (!options.DatabaseName.IsNullOrEmptyOrWhitespace() && !options.AsDatabaseFilePath.IsNullOrEmptyOrWhitespace())
            {
                log.InfoFormat("Deploying database '{0}' using AS definition file '{1}'", options.DatabaseName, options.AsDatabaseFilePath);
                if (options.ModelConnectionStringToDataMart != null)
                {
                    log.InfoFormat("Changing the cube's connection string to {0}", options.ModelConnectionStringToDataMart);
                }

                // deploy the cube
                server.DeployDatabase(
                    options.DatabaseName,
                    options.AsDatabaseFilePath,
                    options.ModelConnectionStringToDataMart
                );
            }
        }

        /// <summary>
        /// Manages the list of partitions.  Creates new and deletes old partitions
        /// </summary>
        /// <param name="server">A connected SSAS server (either Tabular or Multidimensional)</param>
        /// <param name="listOfPartitions">A populated list of partitions to manage</param>
        public void ManagePartitions(IAmoServer server, IEnumerable<IPartition> listOfPartitions)
        {
            PartitionManager pm = new PartitionManager(server, listOfPartitions);

            // validate that the list we got from the server is sensible
            // 1. check the databases exist
            // 2. check that the cubes exist
            // 3. check the measure groups exist
            // If any partition definition is invalid, it will be logged and the record ignored by the CreatePartitions / DeletePartitions steps
            if (!pm.IsPartitionListValid())
                log.Error("Invalid partition information found in SsasPartitions view. See previous error messages for details. These partitions will be ignored.");

            // create any partitions which are missing
            pm.CreatePartitions();

            // delete any partitions which are not listed 
            pm.DeletePartitions();

            // update the cube structure - This is rather like T-SQL COMMIT TRANSACTION
            pm.UpdateCubeMetaData();
        }

        /// <summary>
        /// Process the database(s) depending upon the command-line switch
        /// </summary>
        /// <param name="server">AMO Server</param>
        /// <param name="options">A populated SsasPartitionManagerOptions object containing the command-line options selected</param>
        /// <param name="listOfPartitions">A populated list of partitions to manage</param>
        public void DoProcessingOptions(IAmoServer server, SsasPartitionManagerOptions options, IEnumerable<IPartition> listOfPartitions)
        {
            // process the database or databases depending upon the command-line switch
            if (!options.ProcessOptions.IsNullOrEmptyOrWhitespace())
            {
                switch (options.ProcessOptions.ToLower())
                {
                    case "processall":
                        ProcessAllDatabasesInPartitionList(server, listOfPartitions);
                        break;

                    case "processdeployed":
                        if (!options.DatabaseName.IsNullOrEmptyOrWhitespace())
                        {
                            log.InfoFormat("Processing database '{0}' starting", options.DatabaseName);
                            server.ProcessDatabase(options.DatabaseName);
                            log.InfoFormat("Processing database '{0}' completed", options.DatabaseName);
                        }
                        break;

                    default:
                        // do nothing
                        break;
                }
            }
        }

        #region ProcessAllDatabasesInPartitionList
        /// <summary>
        /// Process each database found in the listOfPartitions using ProcessAll
        /// </summary>
        /// <param name="server">A connected SSAS server (either Tabular or Multidimensional)</param>
        /// <param name="listOfPartitions">A populated and validated list of partitions to manage</param>
        public void ProcessAllDatabasesInPartitionList(IAmoServer server, IEnumerable<IPartition> listOfPartitions)
        {
            if (server == null) throw new ArgumentNullException("server");
            if (listOfPartitions == null) throw new ArgumentNullException("listOfPartitions");

            // ensure SSAS server is in a connected state
            if (!server.Connected) throw new ApplicationException(Partition.SsasServerNotConnected);

            if (listOfPartitions.Count() > 0)
            {
                // First find each database we have been working on
                foreach (string SsasDatabaseName in listOfPartitions.Where(x => x.IsValidPartitionDefinition == true).Select(x => x.DatabaseName).Distinct())
                {
                    log.InfoFormat("Processing database '{0}' starting", SsasDatabaseName);
                    server.ProcessDatabase(SsasDatabaseName);
                    log.InfoFormat("Processing database '{0}' completed", SsasDatabaseName);
                }
            }
            else
            {
                throw new ApplicationException("No partitions listed in listOfPartitions!");
            }
        }
        #endregion
    }

    /// <summary>
    /// Class to hold the command-line options or config options extracted from the application config file
    /// </summary>
    public class SsasPartitionManagerOptions
    {
        // \a
        public string AsDatabaseFilePath { get; set; }
        // \m
        public string ModelConnectionStringToDataMart { get; set; }
        // \d
        public string DatabaseName { get; set; }
        // \p
        public string ProcessOptions { get; set; }
        // \ServerType
        public string ServerType { get; set; }

        // \c
        public string ConfigDatabaseConnectionString { get; set; }

        // \Sql1ToListServers
        public string Sql1ToListServers { get; set; }
        // \Sql2ToListPartitions
        public string Sql2ToListPartitions { get; set; }

    }
}
